/**
 @file fileutils.cpp
 
 @author Morgan McGuire, graphics3d.com
 
 @author  2002-06-06
 @edited  2010-02-05
 */

#include <cstring>
#include <cstdio>
#include "G3D/platform.h"
#include "G3D/fileutils.h"
#include "G3D/BinaryInput.h"
#include "G3D/BinaryOutput.h"
#include "G3D/g3dmath.h"
#include "G3D/stringutils.h"
#include "G3D/Set.h"
#include "G3D/g3dfnmatch.h"

#include <sys/stat.h>
#include <sys/types.h>
#if _HAVE_ZIP
    #include "zip.h"
#endif

#ifdef G3D_WIN32
   // Needed for _getcwd
   #include <direct.h>
   #include <io.h>
#else
    #include <dirent.h>
    #include <fnmatch.h>
    #include <unistd.h>
    #define _getcwd getcwd
    #define _stat stat
#endif


namespace G3D {
    
namespace _internal {
    Set<std::string> currentFilesUsed;
}

std::string pathConcat(const std::string& dirname, const std::string& file) {
    // Ensure that the directory ends in a slash
    if ((dirname.size() != 0) && 
        (dirname[dirname.size() - 1] != '/') &&
        (dirname[dirname.size() - 1] != '\\') &&
        (dirname[dirname.size() - 1] != ':')) {
        return dirname + '/' + file;
    } else {
        return dirname + file;
    }
}

std::string resolveFilename(const std::string& filename) {
    if (filename.size() >= 1) {
        if ((filename[0] == '/') || (filename[0] == '\\')) {
            // Already resolved
            return filename;
        } else {

            #ifdef G3D_WIN32
                if ((filename.size() >= 2) && (filename[1] == ':')) {
                    // There is a drive spec on the front.
                    if ((filename.size() >= 3) && ((filename[2] == '\\') || 
                                                   (filename[2] == '/'))) {
                        // Already fully qualified
                        return filename;
                    } else {
                        // The drive spec is relative to the
                        // working directory on that drive.
                        debugAssertM(false, "Files of the form d:path are"
                                     " not supported (use a fully qualified"
                                     " name).");
                        return filename;
                    }
                }
            #endif
        }
    }

    char buffer[1024];

    // Prepend the working directory.
    _getcwd(buffer, 1024);

    return format("%s/%s", buffer, filename.c_str());
}

bool zipfileExists(const std::string& filename) {
	std::string	outZipfile;
	std::string	outInternalFile;
    return zipfileExists(filename, outZipfile, outInternalFile);
}

std::string readWholeFile(
    const std::string& filename) {

    _internal::currentFilesUsed.insert(filename);

    std::string s;

    debugAssert(filename != "");
    if (fileExists(filename, false)) {

        int64 length = fileLength(filename);

        char* buffer = (char*)System::alignedMalloc(length + 1, 16);
        debugAssert(buffer);
        FILE* f = fopen(filename.c_str(), "rb");
        debugAssert(f);
        int ret = fread(buffer, 1, length, f);
	    debugAssert(ret == length);(void)ret;
        fclose(f);

        buffer[length] = '\0';    
        s = std::string(buffer);

        System::alignedFree(buffer);

    } else if (zipfileExists(filename)) {

        void* zipBuffer;
        size_t length;
        zipRead(filename, zipBuffer, length);

        char* buffer = (char*)System::alignedMalloc(length + 1, 16);
        System::memcpy(buffer,zipBuffer, length + 1);
        zipClose(zipBuffer);

        buffer[length] = '\0';
        s = std::string(buffer);
        System::alignedFree(buffer);
    } else {
        debugAssertM(false, filename + " not found");
    }

    return s;
}


void zipRead(const std::string& file,
             void*& data,
             size_t& length) {
    std::string zip, desiredFile;
#if _HAVE_ZIP    
    if (zipfileExists(file, zip, desiredFile)) {
        struct zip *z = zip_open( zip.c_str(), ZIP_CHECKCONS, NULL );
        {
            struct zip_stat info;
            zip_stat_init( &info );    // TODO: Docs unclear if zip_stat_init is required.
            zip_stat( z, desiredFile.c_str(), ZIP_FL_NOCASE, &info );
            length = info.size;
            // sets machines up to use MMX, if they want
            data = System::alignedMalloc(length, 16);
            struct zip_file *zf = zip_fopen( z, desiredFile.c_str(), ZIP_FL_NOCASE );
            {
                int test = zip_fread( zf, data, length );
                debugAssertM((size_t)test == length,
                             desiredFile + " was corrupt because it unzipped to the wrong size.");
                (void)test;
            }
            zip_fclose( zf );
        }
        zip_close( z );
    } else {
        data = NULL;
    }
#else
    data = NULL;
#endif
}


void zipClose(void* data) {
	System::alignedFree(data);
}


int64 fileLength(const std::string& filename) {
    struct _stat st;
    int result = _stat(filename.c_str(), &st);
    
    if (result == -1) {
#if _HAVE_ZIP
		std::string zip, contents;
		if(zipfileExists(filename, zip, contents)){
			int64 requiredMem;

                        struct zip *z = zip_open( zip.c_str(), ZIP_CHECKCONS, NULL );
                        debugAssertM(z != NULL, zip + ": zip open failed.");
			{
                                struct zip_stat info;
                                zip_stat_init( &info );    // TODO: Docs unclear if zip_stat_init is required.
                                int success = zip_stat( z, contents.c_str(), ZIP_FL_NOCASE, &info );
                                debugAssertM(success == 0, zip + ": " + contents + ": zip stat failed.");
                                requiredMem = info.size;
			}
                        zip_close( z );
			return requiredMem;
		} else {
        return -1;
		}
#else
        return -1;
#endif
    }

    return st.st_size;
}

/** Used by robustTmpfile.  Returns nonzero if fread, fwrite, and fseek all
succeed on the file.
  @author Morgan McGuire, http://graphics.cs.williams.edu  */
static int isFileGood(FILE* f) {

	int x, n, result;

	/* Must be a valid file handle */
	if (f == NULL) {
		return 0;
	}

	/* Try to write */
	x = 1234;
	n = fwrite(&x, sizeof(int), 1, f);

	if (n != 1) {
		return 0;
	}

	/* Seek back to the beginning */
	result = fseek(f, 0, SEEK_SET);
	if (result != 0) {
		return 0;
	}

	/* Read */
	n =	fread(&x, sizeof(int), 1, f);
	if (n != 1) {
		return 0;
	}

	/* Seek back to the beginning again */
	fseek(f, 0, SEEK_SET);

	return 1;
}

FILE* createTempFile() {
    FILE* t = NULL;

//#   ifdef G3D_WIN32
        t = tmpfile();
//#   else
//        // On Unix, tmpfile generates a warning for any code that links against it.
//        const char* tempfilename = "/tmp/g3dtemp.XXXXXXXX";
//        mktemp(tempfilename);
//        t = fopen(tempfilename, "w");    
//#   endif

#	ifdef _WIN32
		char* n = NULL;
#	endif
	char name[256];

    if (isFileGood(t)) {
        return t;
    }

#   ifdef G3D_WIN32
    /* tmpfile failed; try the tmpnam routine */
    t = fopen(tmpnam(NULL), "w+");
    if (isFileGood(t)) {
        return t;
    }

    n = _tempnam("c:/tmp/", "t");
    /* Try to create something in C:\tmp */
    t = fopen(n, "w+");
    if (isFileGood(t)) {
        return t;
    }

    /* Try c:\temp */
    n = _tempnam("c:/temp/", "t");
    t = fopen(n, "w+");
    if (isFileGood(t)) {
        return t;
    }

    /* try the current directory */
    n = _tempnam("./", "t");
    t = fopen(n, "w+");
    if (isFileGood(t)) {
        return t;
    }

    sprintf(name, "%s/tmp%d", "c:/temp", rand());
    t = fopen(name, "w+");
    if (isFileGood(t)) {
        return t;
    }

    /* Try some hardcoded paths */
    sprintf(name, "%s/tmp%d", "c:/tmp", rand());
    t = fopen(name, "w+");
    if (isFileGood(t)) {
        return t;
    }
#   else
    sprintf(name, "%s/tmp%d", "/tmp", rand());
    t = fopen(name, "w+");
    if (isFileGood(t)) {
        return t;
    }
#endif

    sprintf(name, "tmp%d", rand());
    t = fopen(name, "w+");
    if (isFileGood(t)) {
        return t;
    }

    fprintf(stderr, "Unable to create a temporary file; robustTmpfile returning NULL\n");

    return NULL;
}

///////////////////////////////////////////////////////////////////////////////
void writeWholeFile(
    const std::string&          filename,
    const std::string&          str,
    bool                        flush) {

    // Make sure the directory exists.
    std::string root, base, ext, path;
    Array<std::string> pathArray;
    parseFilename(filename, root, pathArray, base, ext); 

    path = root + stringJoin(pathArray, '/');
    if (! fileExists(path, false)) {
        createDirectory(path);
    }

    FILE* file = fopen(filename.c_str(), "wb");

    debugAssert(file);

    fwrite(str.c_str(), str.size(), 1, file);

    if (flush) {
        fflush(file);
    }
    fclose(file);
}

///////////////////////////////////////////////////////////////////////////////

/**
 Creates the directory (which may optionally end in a /)
 and any parents needed to reach it.
 */
void createDirectory(
    const std::string&  dir) {
    
	if (dir == "") {
		return;
	}

    std::string d;

    // Add a trailing / if there isn't one.
    switch (dir[dir.size() - 1]) {
    case '/':
    case '\\':
        d = dir;
        break;

    default:
        d = dir + "/";
    }

    // If it already exists, do nothing
    if (fileExists(d.substr(0, d.size() - 1)), false) {
        return;
    }

    // Parse the name apart
    std::string root, base, ext;
    Array<std::string> path;

    std::string lead;
    parseFilename(d, root, path, base, ext);
    debugAssert(base == "");
    debugAssert(ext == "");

    // Begin with an extra period so "c:\" becomes "c:\.\" after
    // appending a path and "c:" becomes "c:.\", not root: "c:\"
    std::string p = root + ".";

    // Create any intermediate that doesn't exist
    for (int i = 0; i < path.size(); ++i) {
        p += "/" + path[i];
        if (! fileExists(p, false)) {
            // Windows only requires one argument to mkdir,
            // where as unix also requires the permissions.
#           ifndef G3D_WIN32
                mkdir(p.c_str(), 0777);
#	    else
                _mkdir(p.c_str());
#	    endif
        }
    }
}

///////////////////////////////////////////////////////////////////////////////

class FileSystemCache {
private:

    Table<std::string, Array<std::string> > m_files;

public:

    bool fileExists(const std::string& filename) {
        const std::string& path = resolveFilename(filenamePath(filename));
        const std::string& name = filenameBaseExt(filename);
        
        bool neverBeforeSeen = false;
        Array<std::string>& fileList = m_files.getCreate(path, neverBeforeSeen);
        if (neverBeforeSeen) {
            if (! G3D::fileExists(path, true, false)) {
                // The path itself doesn't exist... back out our insertion (which makes fileList& invalid) 
                m_files.remove(path);
                return false;
            }

            std::string spec = pathConcat(path, "*");

            // Will automatically recurse into zipfiles
            getFiles(spec, fileList);
            getDirs(spec, fileList);

#           ifdef G3D_WIN32 
            {
                // Case insensitive
                for (int i = 0; i < fileList.size(); ++i) {
                    fileList[i] = toLower(fileList[i]);
                }
            }
#           endif
        }

        if (filenameContainsWildcards(name)) {
            // See if anything matches
            for (int i = 0; i < fileList.size(); ++i) {
                if (g3dfnmatch(name.c_str(), fileList[i].c_str(), 0) == 0) {
                    return true;
                }
            }
            return false;
        } else {
            // On windows, this is a lower-lower comparison, so it is case insensitive
            return fileList.contains(name);
        }
    }

    void clear() {
        m_files.clear();
    }

    static FileSystemCache& instance() {
        static FileSystemCache i;
        return i;
    }
};


void clearFileSystemCache() {
    FileSystemCache::instance().clear();
}

bool fileExists
(const std::string&	_filename,
 bool                   lookInZipfiles,
 bool                   trustCache) {
    
    if (_filename.empty()) {
        return false;
    }

    // Remove trailing slash from directories
    const std::string& filename = (endsWith(_filename, "/") || endsWith(_filename, "\\")) ? _filename.substr(0, _filename.length() - 1) : _filename;

    if (trustCache && lookInZipfiles) {
#       ifdef G3D_WIN32
            // Case insensitive
            return FileSystemCache::instance().fileExists(toLower(filename));
#       else
            return FileSystemCache::instance().fileExists(filename);
#       endif
    }

    // Useful for debugging
    //char curdir[1024]; _getcwd(curdir, 1024); 

    struct _stat st;
    int ret = _stat(filename.c_str(), &st);

    // _stat returns zero on success
    bool exists = (ret == 0);

    if (! exists && lookInZipfiles) {
		// Does not exist standalone, but might exist in a zipfile

		// These output arguments will be ignored
		std::string zipDir, internalPath;
		return zipfileExists(filename, zipDir, internalPath);
    } else {
    	return exists;
    }
}

///////////////////////////////////////////////////////////////////////////////

#if _HAVE_ZIP
/* Helper methods for zipfileExists()*/
// Given a string (the drive) and an array (the path), computes the directory
static void _zip_resolveDirectory(std::string& completeDir, const std::string& drive, const Array<std::string>& path, const int length){
	completeDir = drive;
	int tempLength;
	// if the given length is longer than the array, we correct it
	if(length > path.length()){
		tempLength = path.length();
	} else{
		tempLength = length;
	}

	for(int t = 0; t < tempLength; ++t){
		if(t > 0){
			completeDir += "/";
		}
		completeDir += path[t];
	}
}


// assumes that zipDir references a .zip file
static bool _zip_zipContains(const std::string& zipDir, const std::string& desiredFile){
        struct zip *z = zip_open( zipDir.c_str(), ZIP_CHECKCONS, NULL );
	//the last parameter, an int, determines case sensitivity:
	//1 is sensitive, 2 is not, 0 is default
        int test = zip_name_locate( z, desiredFile.c_str(), ZIP_FL_NOCASE );
        zip_close( z );
	if(test == -1){
		return false;
	}
	return true;
}
#endif

// If no zipfile exists, outZipfile and outInternalFile are unchanged
bool zipfileExists(const std::string& filename, std::string& outZipfile,
                   std::string& outInternalFile){
#if _HAVE_ZIP
    Array<std::string> path;
    std::string drive, base, ext, zipfile, infile;
    parseFilename(filename, drive, path, base, ext);
    
    // Put the filename back together
    if ((base != "") && (ext != "")) {
        infile = base + "." + ext;
    } else {
        infile = base + ext;
    }
    
    // Remove "." from path
    for (int i = 0; i < path.length(); ++i) {
        if (path[i] == ".") {
            path.remove(i);
            --i;
        }
    }
    
    // Remove ".." from path
    for (int i = 1; i < path.length(); ++i) {
        if ((path[i] == "..") && (i > 0) && (path[i - 1] != "..")) {
            // Remove both i and i - 1
            path.remove(i - 1, 2);
            i -= 2;
        }
    }
    
    // Walk the path backwards, accumulating pieces onto the infile until
    // we find a zipfile that contains it
    for (int t = 0; t < path.length(); ++t){
        _zip_resolveDirectory(zipfile, drive, path,  path.length() - t);
        if (t > 0) {
            infile = path[path.length() - t] + "/" + infile;
        }

        if (endsWith(zipfile, "..")) {
            return false;
        }
        
        if (fileExists(zipfile, false)) {
            // test if it actually is a zipfile
            // if not, return false, a bad
            // directory structure has been given,
            // not a .zip
            if (isZipfile(zipfile)){
                
                if (_zip_zipContains(zipfile, infile)){
                    outZipfile = zipfile;
                    outInternalFile = infile;
                    return true;
                } else {
                    return false;
                }
            } else {
                // the directory structure was valid but did not point to a .zip
                return false;
            }
        }
        
    }
#endif
    // not a valid directory structure ever, 
    // obviously no .zip was found within the path 
    return false;
}	

///////////////////////////////////////////////////////////////////////////////

std::string generateFilenameBase(const std::string& prefix, const std::string& suffix) {
    Array<std::string> exist;

    // Note "template" is a reserved word in C++
    std::string templat = prefix + System::currentDateString() + "_";
    getFiles(templat + "*", exist);
    
    // Remove extensions
    for (int i = 0; i < exist.size(); ++i) {
        exist[i] = filenameBase(exist[i]);
    }

    int num = 0;
    std::string result;
    templat += "%03d" + suffix;
    do {
        result = format(templat.c_str(), num);
        ++num;
    } while (exist.contains(result));

    return result;
}

///////////////////////////////////////////////////////////////////////////////

void copyFile(
    const std::string&          source,
    const std::string&          dest) {

    #ifdef G3D_WIN32
        CopyFileA(source.c_str(), dest.c_str(), FALSE);
    #else
        // TODO: don't use BinaryInput and BinaryOutput
        // Read it all in, then dump it out
        BinaryInput  in(source, G3D_LITTLE_ENDIAN);
        BinaryOutput out(dest, G3D_LITTLE_ENDIAN);
        out.writeBytes(in.getCArray(), in.size());
        out.commit(false);
    #endif
}

//////////////////////////////////////////////////////////////////////////////

void parseFilename(
    const std::string&  filename,
    std::string&        root,
    Array<std::string>& path,
    std::string&        base,
    std::string&        ext) {

    std::string f = filename;

    root = "";
    path.clear();
    base = "";
    ext = "";

    if (f == "") {
        // Empty filename
        return;
    }

    // See if there is a root/drive spec.
    if ((f.size() >= 2) && (f[1] == ':')) {
        
        if ((f.size() > 2) && isSlash(f[2])) {
        
            // e.g.  c:\foo
            root = f.substr(0, 3);
            f = f.substr(3, f.size() - 3);
        
        } else {
        
            // e.g.  c:foo
            root = f.substr(2);
            f = f.substr(2, f.size() - 2);

        }

    } else if ((f.size() >= 2) & isSlash(f[0]) && isSlash(f[1])) {
        
        // e.g. //foo
        root = f.substr(0, 2);
        f = f.substr(2, f.size() - 2);

    } else if (isSlash(f[0])) {
        
        root = f.substr(0, 1);
        f = f.substr(1, f.size() - 1);

    }

    // Pull the extension off
    {
        // Find the period
        size_t i = f.rfind('.');

        if (i != std::string::npos) {
            // Make sure it is after the last slash!
            size_t j = iMax(f.rfind('/'), f.rfind('\\'));
            if ((j == std::string::npos) || (i > j)) {
                ext = f.substr(i + 1, f.size() - i - 1);
                f = f.substr(0, i);
            }
        }
    }

    // Pull the basename off
    {
        // Find the last slash
        size_t i = iMax(f.rfind('/'), f.rfind('\\'));
        
        if (i == std::string::npos) {
            
            // There is no slash; the basename is the whole thing
            base = f;
            f    = "";

        } else if ((i != std::string::npos) && (i < f.size() - 1)) {
            
            base = f.substr(i + 1, f.size() - i - 1);
            f    = f.substr(0, i);

        }
    }

    // Parse what remains into path.
    size_t prev, cur = 0;

    while (cur < f.size()) {
        prev = cur;
        
        // Allow either slash
        size_t i = f.find('/', prev + 1);
        size_t j = f.find('\\', prev + 1);
        if (i == std::string::npos) {
            i = f.size();
        }

        if (j == std::string::npos) {
            j = f.size();
        }

        cur = iMin(i, j);

        if (cur == std::string::npos) {
            cur = f.size();
        }

        path.append(f.substr(prev, cur - prev));
        ++cur;
    }
}


/**
 Helper for getFileList and getDirectoryList.

 @param wantFiles       If false, returns the directories, otherwise
                        returns the files.
 @param includePath     If true, the names include paths
 */
static void getFileOrDirListNormal
(const std::string&	filespec,
 Array<std::string>&	files,
 bool			wantFiles,
 bool                   includePath) {
    
    bool test = wantFiles ? true : false;
    
    std::string path = "";

    // Find the place where the path ends and the file-spec begins
    size_t i = filespec.rfind('/');
    size_t j = filespec.rfind('\\');
    
    // Drive letters on Windows can separate a path
    size_t k = filespec.rfind(':');
    
    if (((j != std::string::npos) && (j > i)) ||
        (i == std::string::npos)) {
        i = j;
    }
    
    if (((k != std::string::npos) && (k > i)) ||
        (i == std::string::npos)) {
        i = k;
    }
    
    // If there is a path, pull it off
    if (i != std::string::npos) {
        path = filespec.substr(0, i + 1);
    }
   
    std::string prefix = path;

    if (path.size() > 0) {
        // Strip the trailing character
        path = path.substr(0, path.size() - 1);
    }

#   ifdef G3D_WIN32
    {
       struct _finddata_t fileinfo;

        long handle = _findfirst(filespec.c_str(), &fileinfo);
        int result = handle;

        while (result != -1) {
            if ((((fileinfo.attrib & _A_SUBDIR) == 0) == test) && 
                strcmp(fileinfo.name, ".") &&
                strcmp(fileinfo.name, "..")) {
                
                if (includePath) {
                    files.append(prefix + fileinfo.name);
                } else {
                    files.append(fileinfo.name);
                }
            }
            
            result = _findnext(handle, &fileinfo);
        }
    }
#   else
    {
        if (path == "") {
            // Empty paths don't work on Unix
            path = ".";
        }

        // Unix implementation
        DIR* dir = opendir(path.c_str());

        if (dir != NULL) {
            struct dirent* entry = readdir(dir);

            while (entry != NULL) {

                // Exclude '.' and '..'
                if ((strcmp(entry->d_name, ".") != 0) &&
                    (strcmp(entry->d_name, "..") != 0)) {
                    
                    // Form a name with a path
                    std::string filename = prefix + entry->d_name;
                    // See if this is a file or a directory
                    struct _stat st;
                    bool exists = _stat(filename.c_str(), &st) != -1;

                    if (exists &&

                        // Make sure it has the correct type
                        (((st.st_mode & S_IFDIR) == 0) == test) &&

                        // Make sure it matches the wildcard
                        (fnmatch(filespec.c_str(),
                                 filename.c_str(),
                                 FNM_PATHNAME) == 0)) {
                        
                        if (includePath) {
                            files.append(filename);
                        } else {
                            files.append(entry->d_name);
                        }
                    }
                }

                entry = readdir(dir);
            }
            closedir(dir);
        }
    }
#   endif
}

#if _HAVE_ZIP
/**
 @param path   The zipfile name (no trailing slash)
 @param prefix Directory inside the zipfile. No leading slash, must have trailing slash if non-empty.
 @param file   Name inside the zipfile that we are testing to see if it matches prefix + "*"
 */
static void _zip_addEntry(const std::string& path,
                          const std::string& prefix,
						  const std::string& file,
						  Set<std::string>& files,
						  bool wantFiles,
						  bool includePath) {

    // Make certain we are within the desired parent folder (prefix)
    if (beginsWith(file, prefix)) {
        // validityTest was prefix/file
        
        // Extract everything to the right of the prefix
        std::string s = file.substr(prefix.length());
        
        if (s == "") {
            // This was the name of the prefix
            return;
        }
        
        // See if there are any slashes
        size_t slashPos = s.find('/');
        
        bool add = false;
        
        if (slashPos == std::string::npos) {
            // No slashes, so s must be a file
            add = wantFiles;
        } else if (! wantFiles) {
            // Not all zipfiles list directories as explicit entries.
            // Because of this, if we're looking for directories and see
            // any path longer than prefix, we must add the subdirectory. 
            // The Set will fix duplicates for us.
            s = s.substr(0, slashPos);
            add = true;
        }
        
        if (add) {
            if (includePath) {
                files.insert(path + "/" + prefix + s);
            } else {
                files.insert(s);
            }
        }
    }
}
#endif

static void getFileOrDirListZip(const std::string& path,
                                const std::string& prefix,
                                Array<std::string>& files,
                                bool wantFiles,
                                bool includePath){
#if _HAVE_ZIP
    struct zip *z = zip_open( path.c_str(), ZIP_CHECKCONS, NULL );

    Set<std::string> fileSet;

    int count = zip_get_num_files( z );
    for( int i = 0; i < count; ++i ) {
        struct zip_stat info;
        zip_stat_init( &info );    // TODO: Docs unclear if zip_stat_init is required.
        zip_stat_index( z, i, ZIP_FL_NOCASE, &info );
        _zip_addEntry(path, prefix, info.name, fileSet, wantFiles, includePath);
    }
    
    zip_close( z );
    
    fileSet.getMembers(files);
#endif
}


static void determineFileOrDirList(
	const std::string&			filespec,
	Array<std::string>&			files,
	bool						wantFiles,
	bool						includePath) {

	// if it is a .zip, prefix will specify the folder within
	// whose contents we want to see
    std::string prefix = "";
    std::string path = filenamePath(filespec);

    if ((path.size() > 0) && isSlash(path[path.size() - 1])) {
        // Strip the trailing slash
        path = path.substr(0, path.length() -1);
    }
    
    if ((path == "") || fileExists(path, false)) {
        if ((path != "") && isZipfile(path)) {
            // .zip should only work if * is specified as the Base + Ext
            // Here, we have been asked for the root's contents
            debugAssertM(filenameBaseExt(filespec) == "*", "Can only call getFiles/getDirs on zipfiles using '*' wildcard");
            getFileOrDirListZip(path, prefix, files, wantFiles, includePath);
        } else {
            // It is a normal directory
            getFileOrDirListNormal(filespec, files, wantFiles, includePath);
        }
    } else if (zipfileExists(filenamePath(filespec), path, prefix)) {
        // .zip should only work if * is specified as the Base + Ext
        // Here, we have been asked for the contents of a folder within the .zip
        debugAssertM(filenameBaseExt(filespec) == "*", "Can only call getFiles/getDirs on zipfiles using '*' wildcard");
        getFileOrDirListZip(path, prefix, files, wantFiles, includePath);
    }
}


void getFiles(const std::string&			filespec,
              Array<std::string>&			files,
              bool					includePath) {
    
    determineFileOrDirList(filespec, files, true, includePath);
}


void getDirs(
	const std::string&			filespec,
	Array<std::string>&			files,
	bool						includePath) {

	determineFileOrDirList(filespec, files, false, includePath);
}


std::string filenameBaseExt(const std::string& filename) {
    int i = filename.rfind("/");
    int j = filename.rfind("\\");

    if ((j > i) && (j >= 0)) {
        i = j;
    }

#   ifdef G3D_WIN32
        j = filename.rfind(":");
        if ((i == -1) && (j >= 0)) {
            i = j;
        }
#   endif

    if (i == -1) {
        return filename;
    } else {
        return filename.substr(i + 1, filename.length() - i);
    }
}


std::string filenameBase(const std::string& s) {
    std::string drive;
    std::string base;
    std::string ext;
    Array<std::string> path;

    parseFilename(s, drive, path, base, ext);
    return base;
}


std::string filenameExt(const std::string& filename) {
    int i = filename.rfind(".");
    if (i >= 0) {
        return filename.substr(i + 1, filename.length() - i);
    } else {
        return "";
    }
}


std::string filenamePath(const std::string& filename) {
    int i = filename.rfind("/");
    int j = filename.rfind("\\");

    if ((j > i) && (j >= 0)) {
        i = j;
    }

#   ifdef G3D_WIN32
        j = filename.rfind(":");
        if ((i == -1) && (j >= 0)) {
            i = j;
        }
#   endif

    if (i == -1) {
        return "";
    } else {
        return filename.substr(0, i+1);
    }
}


bool isZipfile(const std::string& filename) {

	FILE* f = fopen(filename.c_str(), "r");
	if (f == NULL) {
		return false;
	}
	uint8 header[4];
	fread(header, 4, 1, f);
	
	const uint8 zipHeader[4] = {0x50, 0x4b, 0x03, 0x04};
	for (int i = 0; i < 4; ++i) {
		if (header[i] != zipHeader[i]) {
			fclose(f);
			return false;
		}
	}

	fclose(f);
	return true;
}


bool isDirectory(const std::string& filename) {
    struct _stat st;
    bool exists = _stat(filename.c_str(), &st) != -1;
    return exists && ((st.st_mode & S_IFDIR) != 0);
}


bool filenameContainsWildcards(const std::string& filename) {
    return (filename.find('*') != std::string::npos) || (filename.find('?') != std::string::npos);
}


bool fileIsNewer(const std::string& src, const std::string& dst) {
    struct _stat sts;
    bool sexists = _stat(src.c_str(), &sts) != -1;

    struct _stat dts;
    bool dexists = _stat(dst.c_str(), &dts) != -1;

    return sexists && ((! dexists) || (sts.st_mtime > dts.st_mtime));
}


Array<std::string> filesUsed() {
    Array<std::string> f;
    _internal::currentFilesUsed.getMembers(f);
    return f;
}

}

#ifndef G3D_WIN32
  #undef _stat
#endif
